HashTable同HashMap类似,它也是一种散列表,用于存储键值对。本篇将对HashTable源码进行分析。
继承关系和结构
1 | public class Hashtable<K,V> |
Hashtable不同于HashMap继承自Dictionary抽象类,但相同的继承了Map,Cloneable,Serializable接口。成员类似于HashMap,table为散列表,count为元素个数,threshold为阈值,当容量大于等于阈值时扩容,loadFactor为加载因子用于计算阈值。Entry结构和HashMap一样,用于存储键值对。
构造方法
1 | public Hashtable() { |
构造方法默认大小为11 ,加载因子为0.75,阈值= 11*0.75 = 8
添加元素
1 | public synchronized V put(K key, V value) { |
添加的逻辑首先判断value是否为null,同时通过hash计算key的hash值,在hash方法中同样可以看到key值不能为你null,否则同样抛出异常。这点和HashMap不一样,计算完hash后,计算散列表的索引位置,然后遍历bucket,找到对应的key后,更新value值即可,否则判断count是否大于等于阈值,如果是则需要rehash,这个就是扩容的过程,最后为这个新的键值对创建Entry,并放在bucket的头部。
需要注意的是:put方法被synchronized修饰,即它是线程安全的。
移除元素
1 | public synchronized V remove(Object key) { |
移除元素的过程也很简单,同样是通过hash值计算索引后取到对应的bucket,然后遍历找到对应的key值后从bucket删除即可。类似的remove也是线程安全的。
扩容
1 | protected void rehash() { |
Hashtable的扩容操作基本和HashMap是相同的,只不过新的容量=(oldCapacity << 1) + 1,而HashMap中是旧容量的2倍。而且MAX_ARRAY_SIZE 为 Integer.MAX_VALUE - 8.
遍历
1 | Iterator<Map.Entry<K,V>> i = entrySet().iterator(); |
通过迭代器迭代遍历的一般形式,当然也可以通过keySet,进行迭代。这里我们看看entrySet的实现即可。
1 | public Set<Map.Entry<K,V>> entrySet() { |
entrySet会创建一个EntrySet对象
1 | private class EntrySet extends AbstractSet<Map.Entry<K,V>> { |
iterator会通过getIterator返回一个迭代器
1 | private <T> Enumeration<T> getEnumeration(int type) { |
Hashtable支持Enumberation和Iterator的遍历方式,Enumberation是比较旧的遍历方式,Hashtable中使用Enumberation是历史问题,同时Hashtable支持Iterator,这两种不同的迭代方式都是通过Enumberator实现的。实际是使用了标记做区分,false为Enumberation,true为Iterator.
1 | private class Enumerator<T> implements Enumeration<T>, Iterator<T> { |
可以看到对于Enumberation是不支持remove的,而Iterator支持remove。next方法会取出迭代器下一个可用的元素,它是通过nextElement实现的,hasNext则是判断迭代器中还有没有可用的元素,它通过hasMoreElements实现的。下面我们具体分析
1 | public boolean hasMoreElements() { |
初始情况下entry为null,index为table.length,可以看出hasMoreElements从散列表的后向开始遍历,判断当前entry是否为null,为null则切换bucket继续查找,不为null返回true.
1 | public T nextElement() { |
nextElement用来获取可用的元素,这里可以获取到可用元素的条件是hasMoreElements返回true,即entry不为null,获取到后根据指定的type分别取到key,value或者entry返回,并且将entry赋为e.next即下个Entry。